using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using System.Text;

namespace BF
{
    public class RequestKit
    {
        private static RequestKit _default;
        public static RequestKit Default
        {
            get
            {
                if (_default == null)
                {
                    _default = new RequestKit("RequestKit");
                }
                return _default;
            }
        }


        private readonly string _tag;
        private readonly float _retryDelay;
        private Func<string, string> _funcRespTextFix;
        private Func<string> _funcAuthorization;

        private int _timeout = 20;

        public RequestKit(string tag, float retryDelay = 0)
        {
            this._tag = tag;
            this._retryDelay = retryDelay;
        }

        public void SetTimeout(int timeout)
        {
            _timeout = timeout;
        }

        public void SetRespTextFix(Func<string, string> respTextFix)
        {
            this._funcRespTextFix = respTextFix;
        }

        public void SetAuthorization(Func<string> getAuthorization)
        {
            this._funcAuthorization = getAuthorization;
        }

        #region Post Get
        public void DoPost<T>(string url, WWWForm form, Action<bool, T> onCompletion, int retry = 2)
        {
            DoPost(url, form, (isSuccess, str) => {
                if (isSuccess)
                {
                    T data = default(T);
                    try
                    {
                        data = JsonConvert.DeserializeObject<T>(str);
                    }
                    catch (Exception e)
                    {
                        LogError($"[DoPost] JsonDeserialze failed:\n url: {url},\n resp:{str}");
                        throw e;
                    }

                    onCompletion?.Invoke(isSuccess, data);
                }
                else
                {
                    onCompletion?.Invoke(isSuccess, default(T));
                }
            }, retry);
        }

        public void DoPost(string url, WWWForm form, Action<bool, string> onCompletion, int retry = 2)
        {
            UnityWebRequest request = UnityWebRequest.Post(url, form);
            request.timeout = _timeout;
            DoRequestText(url, request, onCompletion, retry, () => { DoPost(url, form, onCompletion, retry - 1); });
        }


        public void DoGet<T>(string url, Action<bool, T> onCompletion, int retry = 2)
        {
            DoGet(url, (isSuccess, str) => {
                if (isSuccess)
                {
                    T data = default(T);
                    try
                    {
                        data = JsonConvert.DeserializeObject<T>(str);
                    }
                    catch (Exception e)
                    {
                        LogError($"[DoGet] JsonDeserialze failed:\n url: {url},\n resp:{str}");
                        throw e;
                    }

                    onCompletion?.Invoke(isSuccess, data);
                }
                else
                {
                    onCompletion?.Invoke(isSuccess, default(T));
                }
            }, retry);
        }

        public void DoGet(string url, Action<bool, string> onCompletion, int retry = 2)
        {
            UnityWebRequest request = UnityWebRequest.Get(url);
            request.timeout = _timeout;
            DoRequestText(url, request, onCompletion, retry, () => { DoGet(url, onCompletion, retry - 1); });
        }


        public void GetTexture(string url, Action<bool, Texture2D> onCompletion, int retry = 2)
        {
            UnityWebRequest request = UnityWebRequestTexture.GetTexture(url, true);
            request.timeout = _timeout;
            DoRequest(url, request, (isSuccess) => {
                if (!isSuccess)
                {
                    onCompletion?.Invoke(false, null);
                }
                else
                {
                    DownloadHandlerTexture downloadHandler = request.downloadHandler as DownloadHandlerTexture;
                    Texture2D texture2D = downloadHandler.texture;
                    onCompletion?.Invoke(true, texture2D);
                }
            }, retry, () => GetTexture(url, onCompletion, retry - 1));
        }

        private void DoRequestText(string url, UnityWebRequest request, Action<bool, string> onCompletion, int retry,
        Action retryAction = null)
        {

            DoRequest(url, request, (isSuccess) => {
                if (!isSuccess)
                {
                    onCompletion?.Invoke(false, request.error);
                }
                else
                {
                    string downloadText = "";
                    if (GZipUtil.IsGZipData(request.downloadHandler.data))
                    {
                        downloadText = Encoding.UTF8.GetString(GZipUtil.Decompress(request.downloadHandler.data));
                    }
                    else
                    {
                        downloadText = request.downloadHandler.text;
                    }
#if DEBUG
                    Log($"[Resp] {url}---> {downloadText}");
#endif
                    if (_funcRespTextFix != null)
                    {
                        downloadText = _funcRespTextFix?.Invoke(downloadText);
                    }
                    onCompletion?.Invoke(true, downloadText);
                }
            }, retry, retryAction);
        }

        private void DoRequest(string url, UnityWebRequest request, Action<bool> onCompletion, int retry, Action retryAction = null)
        {
            string authorization = _funcAuthorization?.Invoke();
            if (!string.IsNullOrEmpty(authorization))
            {
                request.SetRequestHeader("Authorization", authorization);
            }
            request.SetRequestHeader("Accept-Encoding", "gzip");

            UnityWebRequestAsyncOperation asyncOper = request.SendWebRequest();
            asyncOper.completed += (async) => {
                bool isError = !string.IsNullOrEmpty(request.error) || request.responseCode != 200;
                if (isError)
                {
                    if (retry <= 0 || retryAction == null)
                    {
                        LogError($"[Resp] {url}---> {request.responseCode}, {request.error}");


                        onCompletion?.Invoke(false);
                    }
                    else
                    {
                        if (_retryDelay > 0)
                        {
                            Timer.Pop().Delay(_retryDelay).Action(() => {
                                retryAction();
                            }).Execute();
                        }
                        else
                        {
                            retryAction.Invoke();
                        }
                    }
                }
                else
                {
                    onCompletion?.Invoke(true);
                }
                request.Dispose();
            };
        }
        #endregion

        [System.Diagnostics.Conditional("DEBUG")]
        public void Log(string info)
        {
#if DEBUG
            Debug.Log($"[{_tag}] {info}");
#endif
        }

        public void LogError(string info)
        {
            Debug.LogError($"[{_tag}] {info}");
        }
    }
}
